/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package pardal.algorithms;

import java.util.ArrayList;
import java.util.Random;
import javax.swing.JOptionPane;
import pardal.core.ManipulationHMLP;
import weka.classifiers.AbstractClassifier;
import weka.classifiers.Classifier;
import weka.core.Attribute;
import weka.core.Instances;

/**
 *
 * @author Valdigleis
 */
public class HMLP {
    
    //O Classificador base
    private Classifier classifierBase;
    //A lista de classificadores usados no HMLP
    private Classifier classifiers[];
    //intânicas do problema
    private Instances instances;
    //Objeto de manipulação
    private ManipulationHMLP mHMLP;
    //Founds para o cross-validation
    private int numberCross;
    //porcentagem para treinamento teste
    private double numberPercent;
    //marcador se a predição é até os nós folhas ou não.
    private boolean inLeaf;
    //valor do limiar para aborta a classificação nos nós não folhas
    private double limiar;
    //número de erros e acertos na classificação
    private int ok[], err[];
    //texto de resultados.
    private String textResult;
    

    /**
     * Construtor do HMLP para trabalhar com treinamento em porcentagem da base de dados.
     * 
     * @param classifierBase - O Classificador base para cada nível.
     * @param instances - As intânicas do problema.
     * @param numberPercent - A porcentagem de treino
     * @param limiar  - Valor do limiar de classificação
     * @param inLeaf - se a predição sera nas folhas ou não.
     */
    public HMLP(Classifier classifierBase, Instances instances, double numberPercent, double limiar, boolean inLeaf) {
        try{
            this.mHMLP = new ManipulationHMLP(instances);
            this.classifierBase = classifierBase;
            this.classifiers = AbstractClassifier.makeCopies(classifierBase, this.mHMLP.getHeight());
            this.instances = instances;
            this.numberPercent = numberPercent;
            this.inLeaf = inLeaf;
            this.limiar = limiar;
            this.ok = new int[this.mHMLP.getHeight()];
            this.err = new int[this.mHMLP.getHeight()];
            for(int i = 0; i < this.ok.length; i++ ){
                this.ok[i] = 0;
                this.err[i] = 0;
            }
            runPercetofExample();
        }catch(Exception e){
            e.printStackTrace();
            JOptionPane.showMessageDialog(null, "Erro na construção dos classificadores");
        }
        
    }

    /**
     * Construtor do HMLP para trabalhar com treinamento usnado cross-validation na base de dados.
     * 
     * @param classifierBase - O Classificador base para cada nível.
     * @param instances - As intânicas do problema.
     * @param numberCross - Número de founds para o cross-validation.
     * @param limiar  - Valor do limiar de classificação.
     * @param inLeaf - se a predição sera nas folhas ou não.
     */
    public HMLP(Classifier classifierBase, Instances instances, int numberCross, double limiar, boolean inLeaf) {
        try{
            this.mHMLP = new ManipulationHMLP(instances);
            this.classifierBase = classifierBase;
            this.classifiers = AbstractClassifier.makeCopies(classifierBase, this.mHMLP.getHeight());
            this.instances = instances;
            this.numberCross = numberCross;
            this.inLeaf = inLeaf;
            this.limiar = limiar;
            this.ok = new int[this.mHMLP.getHeight()];
            this.err = new int[this.mHMLP.getHeight()];
            for(int i = 0; i < this.ok.length; i++ ){
                this.ok[i] = 0;
                this.err[i] = 0;
            }
            runCrossValidate();
        }catch(Exception e){
            e.printStackTrace();
            JOptionPane.showMessageDialog(null, "Erro na construção dos classificadores");
        }
    }

    /**
     * Construtor a ser usado no modo de treinamento e teste com toda a base de dados.
     * 
     * @param classifierBase - O Classificador base para cada nível.
     * @param instances - As intânicas do problema.
     * @param limiar - Valor do limiar de classificação.
     * @param inLeaf - Se a predição sera nas folhas ou não.
     */
    public HMLP(Classifier classifierBase, Instances instances, double limiar, boolean inLeaf) {
        try{
            this.mHMLP = new ManipulationHMLP(instances);
            this.classifierBase = classifierBase;
            this.classifiers = AbstractClassifier.makeCopies(classifierBase, this.mHMLP.getHeight());
            this.instances = instances;
            this.inLeaf = inLeaf;
            this.limiar = limiar;
            this.ok = new int[this.mHMLP.getHeight()];
            this.err = new int[this.mHMLP.getHeight()];
            for(int i = 0; i < this.ok.length; i++ ){
                this.ok[i] = 0;
                this.err[i] = 0;
            }
            runAllBase();
        }catch(Exception e){
            e.printStackTrace();
            JOptionPane.showMessageDialog(null, "Erro na construção dos classificadores");
        }
    }
    
    private void runAllBase() {
        this.textResult = "======================\n";
        trainAllBase();
        testAllBase();
        createResultText();
    }
    
    private void runPercetofExample(){
        if(this.numberPercent > 1){
            this.numberPercent = this.numberPercent/10;
        }
        double qutrain = this.numberPercent * this.instances.numInstances();
        double quatest = (1 - this.numberPercent) * this.instances.numInstances();
        this.textResult = "======================\nSplit of " + this.numberPercent*100 + "% for train and " + (1 - this.numberPercent)*100 + "% for tests.\n";
        Instances train = new Instances(instances);
        Instances tests = new Instances(instances);
        train.delete();
        tests.delete();
        ArrayList<Integer> nums = new ArrayList<>();
        int i = 0, num;
        Random r = new Random();
        while(i < qutrain){
            num = r.nextInt(this.instances.numInstances());
            if(checkList(nums, num)){
                train.add(this.instances.instance(num));
                i++;
            }
        }
        i = 0;
        while(i < quatest){
            num = r.nextInt(this.instances.numInstances());
            if(checkList(nums, num)){
                tests.add(this.instances.instance(num));
                i++;
            }
        }
        train.setClassIndex(this.instances.classIndex());
        tests.setClassIndex(this.instances.classIndex());
        ManipulationHMLP hm = new ManipulationHMLP(train);
        trainUseInstances(train, hm);
        if(this.inLeaf){
           String aux = "Classification Obrigatory in Nodes Leafs!\n"; 
           this.textResult = this.textResult.concat(aux);
        }else{
           String aux = "Classification not obrigatory in Nodes Leafs!\n";
           this.textResult = this.textResult.concat(aux);
        }
        testUseInstances(tests);
        createResultText();
    }
    
    private void runCrossValidate(){
        this.textResult = "======================\nUsing " + this.numberCross + " folds for cross-validation\n";
        ArrayList<Instances> instancesTrain = new ArrayList<>();
        ArrayList<Instances> instancesTests = new ArrayList<>();
        Instances train, tests;
        for(int i = 0; i < this.numberCross; i++){
            train = this.instances.trainCV(this.numberCross, i);
            tests = this.instances.testCV(this.numberCross, i);
            instancesTrain.add(train);
            instancesTests.add(tests);
        }
        if(this.inLeaf){
           String aux = "Classification Obrigatory in Nodes Leafs!\n"; 
           this.textResult = this.textResult.concat(aux);
        }else{
           String aux = "Classification not obrigatory in Nodes Leafs!\n";
           this.textResult = this.textResult.concat(aux);
        }
        for (int i = 0; i < this.numberCross; i++) {
            for(int j = 0; j < this.numberCross; j++){
                if(j != i){
                    ManipulationHMLP mLP = new ManipulationHMLP(instancesTrain.get(i));
                    trainUseCross(instancesTrain.get(j), mLP);
                }
            }
            testUseInstances(instancesTests.get(i));
        }
        createResultTextCross();
    }
    
    private void trainAllBase(){
        try{
           String aux;
           for(int i = 0; i < this.classifiers.length;i++){
                Instances temp = this.mHMLP.buildInstancesForLevel(i+1);
                temp.setClassIndex(this.instances.classIndex());
                temp.deleteWithMissingClass();
                aux = "In Train of Level " + (i+1) + "\n";
                this.textResult = textResult.concat(aux);
                aux = "Number examples: " + temp.numInstances() + "\n";
                this.textResult = textResult.concat(aux);
                aux = "Number Attributes: " + temp.numAttributes() + "\n-----------------------\n";
                this.textResult = textResult.concat(aux);
                this.classifiers[i].buildClassifier(temp);
           }
        }catch(Exception e){
            JOptionPane.showMessageDialog(null, "Erro no treinamento dos classificadores");
        }
    }
    
    private void testAllBase() {
        if(this.inLeaf){
            try {
                String aux = "Classification Obrigatory in Nodes Leafs!\n";
                this.textResult = this.textResult.concat(aux);
                Instances testInstances = new Instances(this.instances);
                String classeReal;
                String classeLocal;
                for (int i = 0; i < testInstances.numInstances(); i++) {
                    classeReal = this.instances.instance(i).stringValue(this.instances.classIndex());
                    for (int j = 0; j < this.classifiers.length; j++) {
                        double id = this.classifiers[j].classifyInstance(testInstances.instance(i));
                        classeLocal = getClasseInTest(id);
                        if(classeReal.contains(classeLocal) || classeReal.equals(classeLocal)){
                            this.ok[j]++;
                        }else{
                            this.err[j]++;
                        }
                        if(this.mHMLP.nodeIsLeaf(classeLocal)){
                            break;
                        }
                    }
                }
            } catch (Exception e) {
                JOptionPane.showMessageDialog(null, "Erro no teste!");
            }
        }else{
            try {
                String aux = "Classification not obrigatory in Nodes Leafs!\n";
                this.textResult = this.textResult.concat(aux);
                Instances testInstances = new Instances(this.instances);
                String classeReal;
                String classeLocal;
                double values[], max;
                for (int i = 0; i < testInstances.numInstances(); i++) {
                    classeReal = this.instances.instance(i).stringValue(this.instances.classIndex());
                    for (int j = 0; j < this.classifiers.length; j++) {
                        values = this.classifiers[j].distributionForInstance(this.instances.instance(i));
                        max = getValueConfience(values);
                        if(max < this.limiar){
                            if(j == 0){
                                this.err[0]++;
                                break;
                            }else{
                                double id = this.classifiers[j].classifyInstance(testInstances.instance(i));
                                classeLocal = getClasseInTest(id);
                                if(classeReal.contains(classeLocal) || classeReal.equals(classeLocal)){
                                    this.ok[j]++;
                                    break;
                                }else{
                                    this.err[j]++;
                                    break;
                                }
                            }
                        }else{
                            double id = this.classifiers[j].classifyInstance(testInstances.instance(i));
                            classeLocal = getClasseInTest(id);
                            if(classeReal.contains(classeLocal) || classeReal.equals(classeLocal)){
                                this.ok[j]++;
                            }else{
                                this.err[j]++;
                            }
                        }
                    }
                }
            } catch (Exception e) {
                JOptionPane.showMessageDialog(null, "Erro no teste!");
            }
        }
            
    }
    
    private void trainUseInstances(Instances train, ManipulationHMLP Mhmlp){
        try{
           String aux;
           for(int i = 0; i < this.classifiers.length;i++){
                Instances temp = Mhmlp.buildInstancesForLevel(i+1);
                temp.setClassIndex(this.instances.classIndex());
                temp.deleteWithMissingClass();
                aux = "In Train of Level " + (i+1) + "\n";
                this.textResult = textResult.concat(aux);
                aux = "Number examples: " + temp.numInstances() + "\n";
                this.textResult = textResult.concat(aux);
                aux = "Number Attributes: " + temp.numAttributes() + "\n-----------------------\n";
                this.textResult = textResult.concat(aux);
                this.classifiers[i].buildClassifier(temp);
           }
        }catch(Exception e){
            JOptionPane.showMessageDialog(null, "Erro no treinamento dos classificadores");
        }
    }
    
    private void testUseInstances(Instances test){
        if(this.inLeaf){
            try {
                Instances testInstances = new Instances(test);
                String classeReal;
                String classeLocal;
                for (int i = 0; i < testInstances.numInstances(); i++) {
                    classeReal = testInstances.instance(i).stringValue(testInstances.classIndex());
                    for (int j = 0; j < this.classifiers.length; j++) {
                        double id = this.classifiers[j].classifyInstance(testInstances.instance(i));
                        classeLocal = getClasseInTest(id);
                        if(classeReal.contains(classeLocal) || classeReal.equals(classeLocal)){
                            this.ok[j]++;
                        }else{
                            this.err[j]++;
                        }
                        if(this.mHMLP.nodeIsLeaf(classeLocal)){
                            break;
                        }
                    }
                }
            } catch (Exception e) {
                JOptionPane.showMessageDialog(null, "Erro no teste!");
            }
        }else{
            try {
                Instances testInstances = new Instances(test);
                String classeReal;
                String classeLocal;
                double values[], max;
                for (int i = 0; i < testInstances.numInstances(); i++) {
                    classeReal = testInstances.instance(i).stringValue(this.instances.classIndex());
                    for (int j = 0; j < this.classifiers.length; j++) {
                        values = this.classifiers[j].distributionForInstance(testInstances.instance(i));
                        max = getValueConfience(values);
                        if(max < this.limiar){
                            if(j == 0){
                                this.err[0]++;
                                break;
                            }else{
                                double id = this.classifiers[j].classifyInstance(testInstances.instance(i));
                                classeLocal = getClasseInTest(id);
                                if(classeReal.contains(classeLocal) || classeReal.equals(classeLocal)){
                                    this.ok[j]++;
                                    break;
                                }else{
                                    this.err[j]++;
                                    break;
                                }
                            }
                        }else{
                            double id = this.classifiers[j].classifyInstance(testInstances.instance(i));
                            classeLocal = getClasseInTest(id);
                            if(classeReal.contains(classeLocal) || classeReal.equals(classeLocal)){
                                this.ok[j]++;
                            }else{
                                this.err[j]++;
                            }
                        }
                    }
                }
            } catch (Exception e) {
                JOptionPane.showMessageDialog(null, "Erro no teste!");
            }
        }
    }
    
    private void trainUseCross(Instances train, ManipulationHMLP Mhmlp){
        try{
           for(int i = 0; i < this.classifiers.length;i++){
                Instances temp = Mhmlp.buildInstancesForLevel(i+1);
                temp.setClassIndex(this.instances.classIndex());
                temp.deleteWithMissingClass();
                this.classifiers[i].buildClassifier(temp);
           }
        }catch(Exception e){
            JOptionPane.showMessageDialog(null, "Erro no treinamento dos classificadores");
        }
    }
    
    private void createResultText(){
        String aux = "======================\n";
        this.textResult = this.textResult.concat(aux);
        double percent;
        int examples;
        for(int i = 0; i < this.classifiers.length; i++){
            aux = "Test in Level: " + (i+1) + "\n";
            this.textResult = this.textResult.concat(aux);
            examples = this.ok[i] + this.err[i];
            aux = "Number Examples: " + (examples) + "\n";
            this.textResult = this.textResult.concat(aux);
            aux = "Number Erros in Classification: " + (this.err[i]) + "\n";
            this.textResult = this.textResult.concat(aux);
            aux = "Number Sucess in Classification: " + (this.ok[i]) + "\n";
            this.textResult = this.textResult.concat(aux);
            percent = ((100 * this.err[i])/examples);
            aux = "Percentage of Erros in Classification: " + (percent) + "% \n";
            this.textResult = this.textResult.concat(aux);
            percent = ((100 * this.ok[i])/examples);
            aux = "Percentage of Sucess in Classification: " + (percent) + "% \n";
            this.textResult = this.textResult.concat(aux);
            aux = "======================\n";
            this.textResult = this.textResult.concat(aux);
        }
    }

    private void createResultTextCross(){
        String aux = "======================\n";
        this.textResult = this.textResult.concat(aux);
        double percent;
        int examples;
        for(int i = 0; i < this.classifiers.length; i++){
            aux = "Test in Level: " + (i+1) + "\n";
            this.textResult = this.textResult.concat(aux);
            examples = this.ok[i] + this.err[i];
            percent = ((100 * this.err[i])/examples);
            aux = "Percentage of Erros in Classification: " + (percent) + "% \n";
            this.textResult = this.textResult.concat(aux);
            percent = ((100 * this.ok[i])/examples);
            aux = "Percentage of Sucess in Classification: " + (percent) + "% \n";
            this.textResult = this.textResult.concat(aux);
            aux = "======================\n";
            this.textResult = this.textResult.concat(aux);
        }
    }
    
    public String getTextResult() {
        return textResult;
    }
    
    private String getClasseInTest(double id){
        int indexClasse = (int) id;
        Attribute att = this.instances.attribute(this.instances.classIndex());
        String classe = att.value(indexClasse);
        return classe;
    }
    
    private double getValueConfience(double vetor[]){
        double max = 0;
        for (int i = 0; i < vetor.length; i++) {
            if(vetor[i] > max){
                max = vetor[i];
            }
        }
        return max;
    }
    
    private boolean checkList(ArrayList<Integer> nums, int n){
        for (int i = 0; i < nums.size(); i++) {
            if(n == nums.get(i)){
                return false;
            }
        }
        return true;
    }
    
}
